/**
 *
 */
package cc.tutorial.jacob;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.UUID;

import org.apache.commons.lang.SystemUtils;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Dell
 */
public class WordUtils {
    private static final Logger logger = LoggerFactory.getLogger(WordUtils.class);

    /**
     * 功能描述: 将word转换为pdf，注意此方法不会关闭is和os
     * @param is 输入流
     * @param os 输出流
     */
    public static void convertToPdf(InputStream is, OutputStream os) {
        ActionCallBack action = new ActionCallBack() {
            @Override
            public void execute(String srcFilePath, String destFilePath) {
                // PDF 格式
                int wdFormatPDF = 17;
                ActiveXComponent app = null;
                Dispatch doc = null;
                try {
                    app = new ActiveXComponent("Word.Application");
                    app.setProperty("Visible", new Variant(false));
                    Dispatch docs = app.getProperty("Documents").toDispatch();
                    doc = Dispatch.call(docs, "Open", srcFilePath).toDispatch();
                    Dispatch.call(doc, "SaveAs", destFilePath, wdFormatPDF);
                    Dispatch.call(doc, "Close", false);

                } catch (Throwable e) {
                    logger.error("将word转换为pdf错误", e);
                    throw new RuntimeException(e);
                } finally {
                    if (app != null) {
                        app.invoke("Quit", new Variant[]{});
                    }
                    ComThread.Release();
                }
            }
        };

        process(is, os, ".doc", ".pdf", action);
    }

    /**
     * 功能描述: 将word转换为html，注意此方法不会关闭is和os
     * @param is 输入流
     * @param path
     */
    public static void convertToHtml(InputStream is, String path) {
        // 临时文件目录
        File tempDir = new File(SystemUtils.getJavaIoTmpDir().getAbsolutePath(), "wordTemp");
        if (!tempDir.exists()) {
            tempDir.mkdir();
        }

        File srcFile = new File(tempDir, UUID.randomUUID().toString() + ".doc");
        if (srcFile.exists()) {
            srcFile.delete();
        }

        File destFile = new File(tempDir, path);
        if (destFile.exists()) {
            destFile.delete();
        }

        // 从输入流生成src文件
        OutputStream srcOs = null;
        try {
            srcOs = new BufferedOutputStream(new FileOutputStream(srcFile));
            copy(is, srcOs);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            close(srcOs);
        }

        int wdFormatHTML = 8;// HTML 格式
        ActiveXComponent app = null;
        Dispatch doc = null;
        try {
            app = new ActiveXComponent("Word.Application");
            app.setProperty("Visible", new Variant(false));
            Dispatch docs = app.getProperty("Documents").toDispatch();
            doc = Dispatch.call(docs, "Open", srcFile.getAbsolutePath()).toDispatch();
            Dispatch.call(doc, "SaveAs", path, wdFormatHTML);
            Dispatch.call(doc, "Close", false);

        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            if (app != null) {
                app.invoke("Quit", new Variant[]{});
            }
            ComThread.Release();
        }
    }

    /**
     * 功能描述: 替换书签，注意此方法不会关闭is和os
     * @param is           输入流
     * @param os           输出流
     * @param bookmarkList 书签集合
     * @param dataList     替换内容集合
     */
    public static void replaceBookmarks(InputStream is, OutputStream os, final List<String> bookmarkList, final List<String> dataList) {
        ActionCallBack action = new ActionCallBack() {
            @Override
            public void execute(String srcFilePath, String destFilePath) {
                ActiveXComponent app = null;
                Dispatch doc = null;
                try {
                    app = new ActiveXComponent("Word.Application");
                    app.setProperty("Visible", new Variant(false));
                    Dispatch documents = app.getProperty("Documents").toDispatch();
                    doc = Dispatch.call(documents, "Open", srcFilePath).toDispatch();
                    Dispatch activeDocument = app.getProperty("ActiveDocument").toDispatch();
                    Dispatch bookMarks = Dispatch.call(activeDocument, "Bookmarks").toDispatch();

                    // 替换书签
                    for (int i = 0; i < bookmarkList.size(); i++) {
                        boolean bookMarkExist = Dispatch.call(bookMarks, "Exists", bookmarkList.get(i)).changeType(Variant.VariantBoolean).getBoolean();
                        if (bookMarkExist == true) {
                            Dispatch rangeItem = Dispatch.call(bookMarks, "Item", bookmarkList.get(i)).toDispatch();
                            Dispatch range = Dispatch.call(rangeItem, "Range").toDispatch();
                            Dispatch.put(range, "Text", new Variant(dataList.get(i)));
                        }
                    }

                    Dispatch.call(doc, "SaveAs", destFilePath);
                    Dispatch.call(doc, "Close", false);
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                } finally {
                    if (app != null) {
                        app.invoke("Quit", new Variant[]{});
                    }
                    ComThread.Release();
                }
            }
        };

        process(is, os, ".doc", ".doc", action);
    }

    /**
     * 功能描述: 模板方法，做初始化工作，调用回调函数，执行清理工作。
     *
     * @param is
     * @param os
     * @param srcFileExtension
     * @param destFileExtension
     * @param action
     */
    public static void process(InputStream is, OutputStream os, String srcFileExtension, String destFileExtension, ActionCallBack action) {
        // 临时文件目录
        File tempDir = new File(SystemUtils.getJavaIoTmpDir().getAbsolutePath(), "wordTemp");
        if (!tempDir.exists()) {
            tempDir.mkdir();
        }

        File srcFile = new File(tempDir, UUID.randomUUID().toString() + srcFileExtension);
        if (srcFile.exists()) {
            srcFile.delete();
        }

        File destFile = new File(tempDir, UUID.randomUUID().toString() + destFileExtension);
        if (destFile.exists()) {
            destFile.delete();
        }

        // 从输入流生成src文件
        OutputStream srcOs = null;
        try {
            srcOs = new BufferedOutputStream(new FileOutputStream(srcFile));
            copy(is, srcOs);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            close(srcOs);
        }

        action.execute(srcFile.getAbsolutePath(), destFile.getAbsolutePath());

        // 拷贝dest文件到输出流
        InputStream destIs = null;
        try {
            destIs = new BufferedInputStream(new FileInputStream(destFile));
            copy(destIs, os);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            close(destIs);
        }

        // 删除临时文件
        if (srcFile.exists()) {
            srcFile.delete();
        }

        if (destFile.exists()) {
            destFile.delete();
        }
    }

    private interface ActionCallBack {
        void execute(String srcFilePath, String destFilePath);
    }

    /**
     * 功能描述: 将输入流的数据拷贝到输出流
     * @param is
     * @param os
     * @throws IOException
     */
    private static void copy(InputStream is, OutputStream os) throws IOException {
        int len = 0;
        byte[] buffer = new byte[1024 * 4];
        while (-1 != (len = is.read(buffer))) {
            os.write(buffer, 0, len);
        }
    }

    /**
     * 功能描述: 关闭流
     * @param s
     */
    private static void close(Closeable s) {
        if (s != null) {
            try {
                s.close();
            } catch (IOException e) {
                // 忽略
            }
        }
    }
}
